package com.example.common.util;

import java.util.*;

/**
 * 数字转码
 *
 * @author zhb
 * @date 2021-07-31 17:40
 */
public class CodeUtil {

    // 根据种子进行加密的数组
    private static final Map<Byte, byte[]> seedMap = new HashMap<>(16);

    static {
        byte index = 1;
        seedMap.put(index++, new byte[]{0, 1, 0, 3, 7, 7, 2, 2, 2, 3});
        seedMap.put(index++, new byte[]{1, 4, 3, 2, 3, 3, 3, 8, 0, 0});
        seedMap.put(index++, new byte[]{2, 6, 9, 7, 8, 5, 0, 7, 5, 9});
        seedMap.put(index++, new byte[]{8, 7, 5, 1, 9, 6, 1, 9, 8, 4});
        seedMap.put(index++, new byte[]{6, 8, 8, 9, 2, 8, 6, 7, 7, 2});
        seedMap.put(index++, new byte[]{5, 3, 7, 4, 6, 4, 5, 3, 1, 6});
        seedMap.put(index++, new byte[]{7, 5, 2, 5, 5, 1, 4, 1, 2, 4});
        seedMap.put(index++, new byte[]{9, 0, 6, 5, 1, 0, 7, 3, 7, 7});
        seedMap.put(index++, new byte[]{3, 2, 4, 0, 4, 9, 9, 1, 8, 4});
        seedMap.put(index, new byte[]{1, 8, 4, 3, 2, 6, 5, 7, 9, 0});
    }


    // 乘、除、加、减  基数
    private static final int divisor = 10;

    // 产生种子的数组
        private static final byte[] seedArr = {3, 5, 1, 9, 0, 6, 2, 8, 7, 4};
    // 编码--对位转换数组
    private static final byte[] encodeDWArr = {4, 2, 9, 7, 5, 1, 0, 8, 3, 6};


    // 转换之后的前缀
    private static final String PREFIX = "H";
    // 转换过程中，编码数组的长度
    private static final int encodeByteArrayLength = 10;
    private static final int high = 7;
    private static final int low = 3;
    // 转换之后的总长度
    private static final int finalLength = encodeByteArrayLength + 3;

    @Deprecated
    public static void buildSeedArr() {
        // 循环两边 生成 seedArr 和 encodeDWarr
        StringBuilder sb = new StringBuilder("{");
        for (int j=0;j<encodeByteArrayLength;j++) {
            sb.append((int) (Math.random() * divisor)).append(", ");
        }
        System.out.print(sb.substring(0, sb.length() - 2));
        System.out.println("}");

        for (int i = 0; i < seedArr.length; i++) {
            StringBuilder stringBuilder = new StringBuilder("{");
            for (int j = 0; j < encodeByteArrayLength; j++) {
                stringBuilder.append((int) (Math.random() * 10)).append(", ");
            }
            System.out.print(stringBuilder.substring(0, stringBuilder.length() - 2));
            System.out.println("}");
        }
    }

    public static void main(String[] args) {
        System.out.println(encodeLong(123));
        System.out.println(decodeLong("H349716685194"));
    }

    /**
     * 对 10位 以内的数字进行编码
     */
    public static String encodeLong(long value) {
        byte seed = getSeed(value);
        byte[] bytes = CodeUtil.l2Arrays(value);
        CodeUtil.bytesReverse(bytes);

        CodeUtil.duiweizhuan(bytes, true);
        CodeUtil.duiweijiami(bytes);
        CodeUtil.duiweijia(bytes, CodeUtil.randoms(seed));

        byte[] newArr = CodeUtil.newArray(bytes, seed);

        StringBuilder sb = new StringBuilder(PREFIX);
        for (byte b : newArr) {
            sb.append(b);
        }
        return sb.toString();
    }

    public static long decodeLong(String code) {
        // 去空格
        String hCode = StringUtils.trim(code);
        // 长度校验
        if (StringUtils.length(hCode) != finalLength) {
            throw new RuntimeException("编码格式错误");
        }

        // 前缀校验
        if (!hCode.startsWith(PREFIX)) {
            throw new RuntimeException("编码格式错误");
        }

        // 校验其他位是否都是数字
        String substring = hCode.substring(1);
        char[] chars = substring.toCharArray();
        if (!CodeUtil.isNum(chars)) {
            throw new RuntimeException("编码格式错误");
        }

        byte[] bytes = CodeUtil.charsToBytes(chars);

        // 校验输入是否正确
        if (!check(bytes)) {
            throw new RuntimeException("编码格式错误");
        }

        // 获取加密种子
        byte seed = bytes[encodeByteArrayLength];

        // 获取红包ID的变式
        byte[] data = Arrays.copyOf(bytes, encodeByteArrayLength);

        CodeUtil.duiWeiJian(data, CodeUtil.randoms(seed));
        CodeUtil.duiWeiJieMi(data);
        CodeUtil.duiweizhuan(data, false);
        CodeUtil.bytesReverse(data);
        return CodeUtil.array2l(data);
    }

    private static boolean isNum(char[] chars) {
        for (char aChar : chars) {
            if (aChar < 48 || aChar > 57) {
                return false;
            }
        }
        return true;
    }

    /**
     * 字符数组转int数组
     */
    private static byte[] charsToBytes(char[] chars) {
        byte[] bytes = new byte[chars.length];

        for (byte i = 0; i < chars.length; i++) {
            bytes[i] = (byte) (chars[i] - '0');
        }
        return bytes;
    }

    /**
     * 对位减
     */
    private static void duiWeiJian(byte[] dataArr, byte[] seedArr) {
        for (byte i = 0; i < dataArr.length; i++) {
            dataArr[i] = (byte) (dataArr[i] - seedArr[i] >= 0
                    ? dataArr[i] - seedArr[i]
                    : divisor + dataArr[i] - seedArr[i]);
        }
    }

    /**
     * 整数转数组
     */
    private static byte[] l2Arrays(long l) {
        byte[] bytes = new byte[encodeByteArrayLength];
        long j = 1;
        for (int i = encodeByteArrayLength - 1; i >= 0; i--) {
            bytes[i] = (byte) (l / j % divisor);
            j *= divisor;
        }
        return bytes;
    }

    /**
     * 数组转自然数
     */
    private static long array2l(byte[] arr) {

        int len = arr.length;
        long j = 1;
        long result = 0;
        for (int i = len - 1; i >= 0; i--) {
            result += (long) arr[i] * j;
            j *= divisor;
        }
        return result;

    }

    /**
     * byte数组翻转
     */
    private static void bytesReverse(byte[] bytes) {
        int p;
        if (bytes.length % 2 == 1) {
            p = (bytes.length - 1) / 2;
        } else {
            p = bytes.length / 2;
        }

        byte t;
        for (int i = 0; i < p; i++) {
            t = bytes[i];
            bytes[i] = bytes[bytes.length - 1 - i];
            bytes[bytes.length - 1 - i] = t;
        }
    }

    /**
     * 对位转
     *
     * @param bytes     要转的数组
     * @param direction true-正向 false-反向
     */
    private static void duiweizhuan(byte[] bytes, boolean direction) {
        if (direction) {
            for (byte i = 0; i < bytes.length; i++) {
                bytes[i] = encodeDWArr[bytes[i]];
            }
        } else {
            for (byte i = 0; i < bytes.length; i++) {
                for (byte j = 0; j < encodeDWArr.length; j++) {
                    if (bytes[i] == encodeDWArr[j]) {
                        bytes[i] = j;
                        break;
                    }
                }
            }
        }
    }


    /**
     * 对位加密
     */
    public static void duiweijiami(byte[] bytes) {
        for (byte i = 0; i < bytes.length; i++) {
            byte e = bytes[i];
            if (e % 2 == 1) { // 奇数 -high
                bytes[i] = (byte) (e - high >= 0 ? e - high : e + 3);
            } else { // 偶数 +low
                bytes[i] = (byte) (e + low >= (high+low) ? e - high : e + low);
            }
        }
    }

    /**
     * 对位解密
     */
    public static void duiWeiJieMi(byte[] bytes) {
        for (byte i = 0; i < bytes.length; i++) {
            int e = bytes[i];
            if (e % 2 == 1) { // 奇数 +high
                bytes[i] = (byte) (e + high >= ((high+low)) ? e - low : e + high);
            } else { // 偶数 -low
                bytes[i] = (byte) (e - low >= 0 ? e - low : e + high);
            }
        }
    }

    /**
     * 对位加
     */
    private static void duiweijia(byte[] valueArr, byte[] seedArr) {
        for (byte i = 0; i < valueArr.length; i++) {
            valueArr[i] = (byte) (valueArr[i] + seedArr[i] >= divisor
                    ? valueArr[i] + seedArr[i] - divisor
                    : valueArr[i] + seedArr[i]);
        }
    }

    private static byte[] newArray(byte[] oldBytes, byte seed) {
        byte[] newBytes = new byte[encodeByteArrayLength + 2];
        byte[] temp = new byte[encodeByteArrayLength + 1];

        for (byte i = 0; i < oldBytes.length; i++) {
            newBytes[i] = temp[i] = oldBytes[i];
        }

        newBytes[encodeByteArrayLength] = temp[encodeByteArrayLength] = seed;
        newBytes[encodeByteArrayLength + 1] = getCheckNum(temp);

        return newBytes;
    }


    private static byte[] randoms(byte b) {
        return seedMap.get(b);
    }

    /**
     * 获取种子num
     *
     * @param couponId 红包ID
     * @return 种子num
     */
    private static byte getSeed(long couponId) {
        return seedArr[(byte) (couponId % divisor)];
    }


    /**
     * 校验
     *
     * @param arr 包含校验码的字符数组
     * @return true-校验通过
     */
    public static boolean check(byte[] arr) {
        return sum(arr) % divisor == 0;
    }

    /**
     * 计算校验数字
     *
     * @param arr 不含校验数字的数组
     * @return 校验数字
     */
    public static byte getCheckNum(byte[] arr) {
        // 因为是不含校验数字的字符串，为了统一sum方法，在后面补0，不会影响计算
        byte[] temp = Arrays.copyOf(arr, arr.length + 1);
        return (byte) ((divisor - sum(temp) % divisor) % divisor);
    }

    /**
     * 根据Luhn算法计算字符串各位数字之和
     *
     * @param arr 需要计算的数组
     * @return 计算结果
     */
    private static int sum(byte[] arr) {
        int n = arr.length;
        int sum = 0;
        for (int i = n; i >= 1; i--) {
            byte a = arr[n - i];
            // 偶数位乘以2
            if (i % 2 == 0) {
                a *= 2;
            }
            // 十位数和个位数相加，如果不是偶数位，不乘以2，则十位数为0
            sum = sum + a / divisor + a % divisor;
        }
        return sum;
    }
}
